/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * vga.cc																	*
 *																			*
 * Puts stuff onto the screen.												*
 * You'll see a lot of the 'register' keyword and heaps of hacks to make	*
 * this code go as fast as possible. Simple changes sped up the entire GUI	*
 * draw by 5 seconds, an extreme amount for such simple changes!			*
 *																			*
\***************************************************************************/

#include <system.h>
#include <vga.h>
#include <memory.h>
#include <iostream>
#include <gui_txt.h>
#include <winman.h>

VGA::VGA()
{
	// set default LFB to 0xE0000000
	m_lfb = 0xE0000000;
	m_width = 800;
	m_height = 600;
	m_bpp = 32;
	m_bgcol = 0;
}

VGA::VGA( uint_t lfb )
{
	m_lfb = lfb;
}

VGA::~VGA()
{
	m_lfb = 0;
	kfree( (void*) m_dbuff );
}

void VGA::SetBgColor( unsigned int col )
{
	// set the background color
	m_bgcol = col;
}

uint_t VGA::GetXRes()
{
	// return the width
	return m_width;
}

uint_t VGA::GetYRes()
{
	// return the height
	return m_height;
}

uint_t VGA::GetBPP()
{
	// return the number of bits per pixel
	return m_bpp;
}

void VGA::EnterVGAMode( ushort_t xres, ushort_t yres, ushort_t bpp )
{
	// set the width, height and bpp
	m_width = xres;
	m_height = yres;
	m_bpp = bpp;
	
	// enter the mode (it returns the framebuffer address)
	m_lfb = EnterVesaMode( xres, yres, bpp );
	
	// setup the double buffer
	m_dbuff = (uint_t) kmalloc( m_width * m_height * ( m_bpp / 8 ) );
}

void VGA::ClearScreen()
{
	// pointer to the linear frame buffer
	register unsigned int* lfb = (unsigned int*) m_dbuff;
	
	// colors
	register unsigned int fillcol = m_bgcol;
	
	// the maximum size
	register int maxsize = ( m_width * m_height * ( m_bpp / 8 ) ) / (sizeof(m_bgcol)/2);
	
	// clears the screen
	for( register int i = 0; i < maxsize; i++ )
	{
		if( lfb[i] != fillcol )
			lfb[i] = fillcol;
	}
}

// draws a rectangle
void VGA::Rectangle( uint_t x, uint_t y, uint_t w, uint_t h, uint_t color )
{
	// address of a pixel
	register uint_t addr,_addr;
	register uchar_t red,green,blue; // 24-bit color
	register uint_t temp1,temp2,temp3;
	
	// the characteristics of the rectangle
	register int bottom = y + h;
	register int right = x + w;
	
	// set the address
	addr = m_dbuff + ( ( ( y * m_width ) + x ) * ( m_bpp / 8 ) );
	
	// how many bits per pixel are we using?
	switch( m_bpp )
	{
		// 8-bit
		case 8:
		
			// the different colors
			temp1 = color & 0xFF;
			temp1 |= ( temp1 << 8 ) | ( temp1 << 16 ) | ( temp1 << 24 );
			
			// draw the rectangle
			for( register int _y = y; _y < bottom; _y++ )
			{
				// set the address to the right value
				_addr = addr;
				
				// paint...
				while( ( _addr < ( addr + w ) ) && ( ( _addr & 3 ) != 0 ) )
				{
					*((uchar_t*) _addr) = (uchar_t) color;
					_addr++;
				}
				while( ( _addr + 3 ) < ( addr + w ) )
				{
					*((uint_t*) _addr) = temp1;
					_addr += 4;
				}
				while( _addr < ( addr + w ) )
				{
					*((uchar_t*) _addr) = (uchar_t) color;
					_addr++;
				}
				
				// increment the address
				addr += m_width;
			}
			
            break;
		
		// 16-bit
		case 16:
		
			// convert the color to 16-bit
			temp1 = color & 0xFFFF;
			temp1 |= ( temp1 << 16 );
			
			// draw the rectangle
			for( register int _y = y; _y < bottom; _y++ )
			{
				// set the address (counter)
                _addr = addr;
				
				// paint...
				while( ( _addr < ( addr + ( w * 2 ) ) ) && ( ( _addr & 3 ) != 0 ) )
				{
					*((ushort_t*) _addr) = (ushort_t) color;
					_addr += 2;
				}
				while( ( _addr + 3 ) < ( addr + w ) )
				{
					*((uint_t*) _addr) = temp1;
					_addr += 4;
				}
				while( ( _addr < ( addr + ( w * 2 ) ) ) )
				{
					*((ushort_t*) _addr) = (ushort_t) color;
					_addr += 2;
				}
				
				// increment the address
                addr += m_width;
			}
			
            break;
		
		// 24-bit
		case 24:
		
			// divide into rgb
			blue = (uchar_t) color;
			green = (uchar_t) (color >> 8);
			red = (uchar_t) (color >> 16);
			
			// put into the temp variables
			temp1 = blue | (green << 8) | (red << 16) | (blue << 24);
			temp2 = green | (red << 8) | (blue << 16) | (green << 24 );
			temp3 = red | (blue << 8) | (green << 16) | (red  << 24 );
			
			// paint the rectangle
			for( register int _y = y; _y < bottom; _y++ )
			{
				// setup the counter
                _addr = addr;
				
				// paint...
                while( ( _addr < ( addr + (w * 3) ) ) && ( ( _addr % 12 ) != 0 ) )
				{
                    *((uchar_t*) _addr ) = blue;
                    *((uchar_t*) (_addr + 1) ) = green;
                    *((uchar_t*) (_addr + 2) ) = red;
                    _addr += 3;
                }
                while( ( _addr + 11 ) < ( addr + w ) )
				{
                    *((uint_t*) _addr ) = temp1;
                    *((uint_t*) (_addr + 4) ) = temp2;
                    *((uint_t*) (_addr + 8) ) = temp3;
                    _addr += 12;
                }
                while( _addr < ( addr + (w * 3) ) )
				{
                    *((uchar_t*) _addr ) = blue;
                    *((uchar_t*) (_addr + 1) ) = green;
                    *((uchar_t*) (_addr + 2) ) = red;
                    _addr += 3;
                }
				
				// increment the width
                addr += m_width;
			}
			
			break;

		// 32-bit
		case 32:
		
			// draw the rectangle (registers are faster)
			for( register int _y = y; _y < bottom; _y++ )
			{
				// draw the x portion
				for( _addr = addr; _addr < ( addr + ( w * 4 ) ); _addr += 4 )
					*((uint_t*) _addr) = color;
				
				// increment by the total width
				addr += m_width * 4;
			}
			
			break;
	};
}

void VGA::DrawBitmap( uchar_t* data, uint_t x, uint_t y, uint_t w, uint_t h, uint_t isbmp )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offset
	uint_t boffset;
	if( isbmp )
		boffset = w * h * 3;
	else
		boffset = 0;
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		if( isbmp )
		{
			for( int _x = (x+w); _x > x; _x-- )
			{
				// draw, if the x and y aren't too high
				if( _x < m_width && _y < m_height )
				{
					SetPixel( _x, _y, *((uint_t*) &data[boffset]) );
				}
				
				// decrement the offset
				boffset -= 3;
			}
		}
		else
		{
			for( int _x = x; _x < (x+w); _x++ )
			{
				// draw, if the x and y aren't too high
				if( _x < m_width && _y < m_height )
				{
					SetPixel( _x, _y, *((uint_t*) &data[boffset]) );
				}
				
				// increment the offset
				boffset += 3;
			}
		}
	}
}

// color-keyed bitmap draw
void VGA::DrawBitmap( uint_t* data, uint_t x, uint_t y, uint_t w, uint_t h, uint_t colorkey )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offset
	uint_t boffset = 0;
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		for( int _x = x; _x < (x+w); _x++ )
		{
			// draw, if the x and y aren't too high
			if( _x < m_width && _y < m_height )
			{
				if( data[boffset] != colorkey )
					SetPixel( _x, _y, data[boffset] );
			}
			
			// increment the bitmap offset
			boffset++;
		}
	}
}

void VGA::DrawBitmap( uint_t* data, uint_t x, uint_t y, uint_t w, uint_t h )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offset
	uint_t boffset = 0;
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		for( int _x = x; _x < (x+w); _x++ )
		{
			// draw, if the x and y aren't too high
			if( _x < m_width && _y < m_height )
			{
				SetPixel( _x, _y, data[boffset] );
			}
			
			// increment the bitmap offset
			boffset++;
		}
	}
}

// draws a cursor, then returns a pointer to the old data
uint_t* VGA::DrawCursor( uchar_t* data, uint_t x, uint_t y, uint_t w, uint_t h )
{
	// loop through each byte, blitting to screen as we go
	uchar_t* dblbuff = (uchar_t*) m_dbuff;
	
	// the offsets
	uint_t offset = 0;
	uint_t boffset = 0;
	
	// return pointer
	uint_t* ret = (uint_t*) kmalloc( w * h );
	
	// loop through
	for( int _y = y; _y < (y+h); _y++ )
	{
		for( int _x = x; _x < (x+w); _x++ )
		{
			// save the old data (we save all of it so we don't need special handling)
			ret[offset++] = *((uint_t*) m_dbuff + ( ( m_width * _y ) + _x ));
			
			// check if it's valid
			if( ( data[boffset] == 0xFE && data[boffset+1] == 0xFE && data[boffset+2] == 0xFE )
				||
				( data[boffset] == 0x00 && data[boffset+1] == 0x00 && data[boffset+2] == 0x00 )
			)
			{
				// draw, if the x and y aren't too high
				if( _x < m_width && _y < m_height )
				{					
					// set the new data
					SetPixel( _x, _y, *((uint_t*) &data[boffset]) );
				}
			}
			
			// increment the offset
			boffset += 3;
		}
	}
	
	// return the saved data
	return ret;
}

void VGA::SetPixel( uint_t x, uint_t y, uint_t color )
{
	// get the address - rather complex...
	uint_t addr = (uint_t) ( m_dbuff + ( ( ( m_width * y ) + x ) * ( m_bpp / 8 ) ) );
	
	// how many bits per pixel?
	switch( m_bpp )
	{
		// 8 bit
		case 8:
		
			// uchar_t is an 8-bit type
			*((uchar_t*) addr) = (uchar_t) color;
			
			break;
		
		// 16 bit
		case 16:
		
			// ushort_t is a 16-bit type
			*((ushort_t*) addr) = (ushort_t) color;
			
			break;
			
		// 24 bit
		case 24:
		
			// special handling
			*((uchar_t*) addr) = (uchar_t) color;
			*((uchar_t*) (addr+1)) = (uchar_t) ( color >> 8 );
			*((uchar_t*) (addr+2)) = (uchar_t) ( color >> 16 );
			
			break;
		
		// 32 bit
		case 32:
		
			// uint_t is 32 bits
			*((uint_t*) addr) = (uint_t) color;
			
			break;
			
	};
}

void VGA::DrawWindow( int x, int y, int w, int h, char* str, int len )
{
	// border :D
	Rectangle( x - 1, y - 1, w + 2, h + 2, COLOR_BLACK );
	
	// draw the window itself
	Rectangle( x, y, w, h, COLOR_WHITE );
	
	// draw the title bar
	Rectangle( x, y, w, 12, COLOR_RED );
	
	// draw the text
	DrawText( x + 2, y + 2, str, len, COLOR_WHITE );
}

void VGA::BlitToScreen()
{
	// vertical retrace
//	while( inportb( 0x3DA ) & 0x08 );
//	while( inportb( 0x3DA ) & 0x08 );
	
	// these are truly powerful beasts
	register unsigned long long* lfb = (unsigned long long*) m_lfb;
	register unsigned long long* dbuff = (unsigned long long*) m_dbuff;
	
	// the total size
	register int maxsize = ( m_width * m_height * ( m_bpp / 8 ) ) / 8;
	
	// copy from the double buffer
	for( register int i = 0; i < maxsize; i++ )
	{
		// check if they are not equal, if not, then we do need to copy
		//if( lfb[i] != dbuff[i] )
			lfb[i] = dbuff[i];
	}
}

void VGA::Line( uint_t x1, uint_t y1, uint_t x2, uint_t y2, uint_t color )
{
	// holds the choice
	int d;
	
	// dx/dy - differences
	int dx,dy;
	
	// variable increments - direction
	int Eincr, NEincr;
	
	// y increment
	int yincr;
	
	// counter
	int t;
	
	// TODO: factor this to the math file
	#define ABS(a)   ((a) >= 0 ? (a) : -(a))
	
	// get the slope
	dx = ABS( x2 - x1 );
	dy = ABS( y2 - y1 );
	
	// check
	if( dy <= dx )
	{
		// slope between -1 and 1
		if( x2 < x1 )
		{
			t = x2; x2 = x1; x1 = t; // swap x
			t = y2; y2 = y1; y1 = t; // swap y
		}
		
		// is the second higher than the first?
		if( y2 > y1 )
			yincr = 1;
		else
			yincr = -1;
		
		// initial decision
		d = 2 * dy - dx;
		Eincr = 2 * dy;
		NEincr = 2 * ( dy - dx );
		SetPixel( x1, y1, color );
		
		// incrementally determine postions of next pixels
		for( x1++; x1 <= x2; x1++ )
		{
			if( d < 0 )
			{
				d += Eincr;
			}
			else
			{
				d += NEincr;
				y1 += yincr;
			}
			
			// draw it
			SetPixel( x1, y1, color );
		}
	}
	else
	{
		// slope between -1 and 1 (??)
		if( y2 < y1 )
		{
			t = x2; x2 = x1; x1 = t; // swap x
			t = y2; y2 = y1; y1 = t; // swap y
		}
		
		// is the second higher than the first?
		if( x2 > x1 )
			yincr = 1;
		else
			yincr = -1;
		
		d = 2 * dx - dy;
		Eincr = 2 * dx;
		NEincr = 2 * ( dx - dy );
		SetPixel( x1, y1, color );
		
		// incrementally determine postions of next pixels
		for( y1++; y1 <= y2; y1++ )
		{
			if( d < 0 )
			{
				d += Eincr;
			}
			else
			{
				d += NEincr;
				x1 += yincr;
			}
			
			// draw it
			SetPixel( x1, y1, color );
		}
	}
}



/* sdl_picofont

   http://nurd.se/~noname/sdl_picofont

   File authors:
      Fredrik Hultin

   License: GPLv2
*/

// 8-bit font
#define FONTHEIGHT	8
#define FONTWIDTH	8

int VGA::DrawText( int x, int y, char* txt, int len, uint_t color )
{
	// get the font
	unsigned char* fnt = txtGetFont();
	
	// column and row
	unsigned int col = 0,row = 0;
	
	// block of pixels
	unsigned int pixels[64];
	unsigned char isdraw[64];
	
	// parse the string
	for( int i = 0; i < len; i++ )
	{
		// get the character
		unsigned char chr = txt[i];
		
		// handle special characters
		switch( chr )
		{
			case '\n':
				row++;
				col = 0;
				chr = 0;
				break;
			
			case '\r':
				chr = 0;
				break;
			
			case '\t':
				chr = 0;
				col += ( 4 - col % 4 );
				break;
			
			case '\0':
				chr = 0;
				break;

			default:
				col++;
				break;
		}
		
		// current offset into the pixel map
		int pxmap_off = 0;
		
		// check it
		if( chr != 0 )
		{
			// current x/y position
			register int thex,they;

			// get the current x/y position
			thex = ( x + ( col * 8 ) ) - FONTWIDTH;
			they = ( y + ( row * 8 ) ); // - FONTHEIGHT;
			
			// draw the character
			for( register int _y = 0; _y < FONTHEIGHT; _y++ )
			{
				for( register int _x = 0; _x < FONTWIDTH; _x++ )
				{
					// check if we can draw it
					if( fnt[txt[i]*FONTHEIGHT+_y] >> (7-_x) & 1 )
					{
						// yep, good to go...
						SetPixel( thex + _x, they + _y, color );
					}
				}
			}
		}
	}
}
